5.11. Операторы и циклы в Ruby
Операторы и циклы в Ruby
Ruby — язык программирования с динамической типизацией и объектно-ориентированной архитектурой, в которой всё является объектом, включая числа, логические значения и даже nil. Эта фундаментальная особенность определяет и подход к реализации операторов и управляющих конструкций: большинство операторов в Ruby — это методы, вызываемые у объектов. Такой дизайн обеспечивает высокую гибкость и расширяемость, но требует понимания внутренней семантики вызовов и приоритетов.
В данной главе рассматриваются:
- операторы (арифметические, сравнения, логические, побитовые, присваивания и специальные),
- условные конструкции (
if,unless,case), - циклические конструкции (
while,until,loop, итераторы), - связанные особенности: операторы
and/or,nil-коалесценция, сокращённые формы, идиомы Ruby.
Все конструкции приводятся с учётом версии языка Ruby 3.x (актуальной на момент написания), однако основные принципы остаются неизменными начиная с Ruby 1.9.
1. Операторы
В Ruby оператор — это синтаксическая оболочка для вызова метода объекта. Например, выражение a + b интерпретируется как вызов метода + у объекта a с аргументом b: a.+(b). Эта особенность позволяет переопределять поведение операторов в пользовательских классах через переопределение соответствующих методов.
1.1. Арифметические операторы
Арифметические операторы применяются к числовым объектам (Integer, Float, Rational, Complex) и возвращают новый объект соответствующего типа.
| Оператор | Описание | Пример | Эквивалентный вызов |
|---|---|---|---|
+ | Сложение | 5 + 3 → 8 | 5.+(3) |
- | Вычитание | 5 - 3 → 2 | 5.-(3) |
* | Умножение | 4 * 2 → 8 | 4.*(2) |
/ | Деление | 7 / 2 → 3 (целочисленное деление для Integer) | 7./(2) |
% | Остаток от деления (по модулю) | 7 % 3 → 1 | 7.%(3) или 7.modulo(3) |
** | Возведение в степень | 2 ** 3 → 8 | 2.**(3) |
Важные особенности:
- Для целых чисел (
Integer) операция/выполняет целочисленное деление с усечением к нулю (не к минус бесконечности, в отличие от математического деления по модулю). Например,-7 / 3→-2. - Оператор
%в Ruby реализует модульное арифметическое значение, и его поведение определяется методомmodulo. В отличие от языков вроде C или Java, результат%в Ruby всегда имеет тот же знак, что и делитель. Например:7 % 3→17 % -3→-2-7 % 3→2-7 % -3→-1
Это соответствует определениюa.modulo(b) = a - b * (a.div(b)), гдеdiv— целочисленное деление с округлением вниз (floor division).
- Оператор
**может принимать дробные и отрицательные показатели степени, возвращаяFloatпри нецелом результате:4 ** 0.5→2.0,2 ** -1→0.5.
Переполнение целых чисел в Ruby не приводит к ошибке: Integer имеет неограниченную точность (arbitrary-precision integer, как BigInt в других языках). Например, 2 ** 1000 вычисляется без потери точности.
1.2. Операторы сравнения
Операторы сравнения возвращают объекты класса TrueClass, FalseClass или nil. В Ruby только два значения считаются ложными в булевом контексте — false и nil; всё остальное (включая 0, "", []) — истинное.
| Оператор | Описание | Пример |
|---|---|---|
== | Равенство значений | 5 == 5.0 → true |
!= | Неравенство | 3 != 4 → true |
<, >, <=, >= | Упорядоченное сравнение | "apple" < "banana" → true |
<=> | Оператор «космический корабль» (spaceship) — трёхстороннее сравнение | 5 <=> 3 → 1, 3 <=> 5 → -1, 4 <=> 4.0 → 0, 1 <=> "1" → nil |
=== | Оператор «case equality» — используется в case-выражениях, семантика зависит от левого операнда | Integer === 42 → true, /a/ === "bar" → true, Range === 5 — для 1..10 === 5 → true |
Пояснения:
- Оператор
==вызывает метод==, который по умолчанию проверяет равенство объектов по значению (а не по ссылке, в отличие отequal?). Он может быть переопределён — например, вStringучитывает содержимое, регистр и кодировку. - Оператор
<=>должен возвращать-1,0,1илиnil. Если сравнение бессмысленно (например, число и строка), возвращаетсяnil. При реализации пользовательских классов определение<=>позволяет легко подключить миксинComparable, который автоматически предоставляет<,<=,>,>=иbetween?. - Оператор
===не симметричен. Его поведение зависит от класса левого операнда:- Для классов (
Class):Class === objэквивалентенobj.is_a?(Class). - Для диапазонов (
Range):range === valueэквивалентенrange.cover?(value). - Для регулярных выражений (
Regexp):/pattern/ === strэквивалентенstr =~ /pattern/(возвращаетtrue, если совпадение найдено). - Для простых значений (
String,Symbol,Integerи др.) поведение совпадает с==, но это соглашение, а не требование.
- Для классов (
1.3. Логические операторы
Ruby предоставляет два уровня логических операторов: низкоприоритетные (and, or, not) и высокоприоритетные (&&, ||, !). Разница — в приоритете относительно других операторов (в первую очередь =).
| Оператор | Приоритет | Аналог в других языках | Особенности |
|---|---|---|---|
&& | Высокий | && | Чаще используется в выражениях |
| ` | ` | Высокий | |
! | Высокий | ! | Унарный оператор логического отрицания |
and | Низкий | — | Редко используется; иногда применяется для потокового управления (например, do_something and return) |
or | Низкий | — | Аналогично and |
not | Низкий | — | Унарный, редко используется: not true → false |
Короткое замыкание (short-circuit evaluation):
- В
a && b: еслиaложно (falseилиnil),bне вычисляется. - В
a || b: еслиaистинно (неfalseи неnil),bне вычисляется.
Примеры:
x = nil
y = x && x.length # → nil (x ложно, x.length не вызывается)
z = x || "default" # → "default"
w = false || true # → true
Поскольку в Ruby только false и nil ложны, логические операторы часто используются для установки значений по умолчанию или защиты от nil.
1.4. Операторы присваивания
Ruby поддерживает как простое присваивание, так и составные формы.
-
=— простое присваивание. Создаёт локальную переменную (если её нет), не вызывает метод.
Пример:x = 10. Локальная переменная создаётся в момент анализа кода, даже если присваивание не выполнится (например, в веткеif false). -
+=,-=,*=,/=,%=,**=— составные операторы присваивания.
Выражениеa += bэквивалентноa = a + b, то есть вызывает метод+уa, затем присваивает результат переменнойa.
Важно: это не модификация объекта на месте (если только+не реализован как+=), а создание нового объекта и перепривязка переменной. -
||=— оператор условного присваивания («nil-coalescing assignment»).
x ||= yэквивалентноx = x || y, но с семантикой: присвоитьy, только еслиxложно (nilилиfalse).
Часто используется для кэширования:def expensive_value
@expensive_value ||= compute_expensive_value
end -
&&=— аналогично:x &&= y→x = x && y. Присваиваетy, только еслиxистинно. -
=в контексте параллельного присваивания:a, b = 1, 2 # → a = 1, b = 2
a, b = [1, 2] # → a = 1, b = 2 (распаковка массива)
a, *rest = [1, 2, 3] # → a = 1, rest = [2, 3]
Особенность: в Ruby нет операторов инкремента/декремента (++, --). Вместо них используются += 1 или методы succ/next (для Integer, String и др.):
x = 5; x = x.succ → 6; "a".succ → "b".
1.5. Побитовые операторы
Доступны для целых чисел. Все побитовые операторы — методы класса Integer.
| Оператор | Описание | Пример |
|---|---|---|
& | Побитовое И | 5 & 3 → 1 (0b101 & 0b011 = 0b001) |
| | Побитовое ИЛИ | 5 | 3 → 7 (0b101 | 0b011 = 0b111) |
^ | Побитовое исключающее ИЛИ | 5 ^ 3 → 6 |
~ | Побитовое НЕ (унарный) | ~5 → -6 (дополнение до -1 по правилам двух’s complement) |
<< | Арифметический сдвиг влево | 2 << 3 → 16 (умножение на 2³) |
>> | Арифметический сдвиг вправо | 16 >> 2 → 4 |
Замечание: сдвиги работают с отрицательными числами корректно: -8 >> 1 → -4.
1.6. Специальные операторы
-
..и...— операторы диапазонов:a..b— включаетb(«замкнутый» диапазон),a...b— исключаетb(«полуоткрытый» диапазон). Оба создают объекты классаRange. Пример:(1..5).to_a→[1, 2, 3, 4, 5];(1...5).to_a→[1, 2, 3, 4].
-
? :— тернарный условный оператор:
условие ? значение_если_истина : значение_если_ложь.
Пример:x > 0 ? "positive" : "non-positive". -
defined?— унарный оператор (на самом деле — метод верхнего уровня), возвращающий строку с типом выражения илиnil, если выражение не определено:defined?(x) # → nil (если x не определена)
x = 1
defined?(x) # → "local-variable"
defined?(Math::PI) # → "constant"
defined?(1 + 1) # → "expression" -
=>— используется в хэш-литералах (до Ruby 1.9) и вcase/when, но не является самостоятельным оператором в выражениях.
2. Условные конструкции
2.1. if, elsif, else
Конструкция if — основной способ ветвления. В Ruby if — это выражение, а не оператор, то есть возвращает значение последнего вычисленного выражения в выбранной ветке.
Синтаксис:
if условие1
# блок 1
elsif условие2
# блок 2
else
# блок else
end
Особенности:
- Условие может быть любым объектом. Ложными считаются только
falseиnil. - Отсутствие фигурных скобок: блоки определяются ключевыми словами и отступами (по соглашению).
- Возврат значения:
result = if x > 0
"positive"
elsif x < 0
"negative"
else
"zero"
end
Модификаторы if и unless (постфиксная форма):
puts "x положительное" if x > 0
raise "ошибка" unless valid?
Удобны для однострочных проверок. Приоритет ниже, чем у присваивания:
x = y if condition → (x = y) if condition, а не x = (y if condition).
2.2. unless
Эквивалент if not, но читается естественнее при формулировке исключений:
unless user.admin?
redirect_to root_path
end
Поддерживает else, но не поддерживает elsif (для цепочек лучше использовать if).
2.3. case
Многоальтернативная условная конструкция. Имеет две формы.
Форма 1: без аргумента у case — как if/elsif:
case
when x < 0
"отрицательное"
when x == 0
"ноль"
else
"положительное"
end
Форма 2: с аргументом у case — использует оператор === для сравнения:
case x
when Integer
"целое"
when String
"строка"
when 1..10
"от 1 до 10"
when /foo/
"содержит 'foo'"
else
"неизвестно"
end
Здесь Integer === x, String === x, (1..10) === x, /foo/ === x — именно поэтому === так важен.
Возврат значения: как и if, case возвращает значение последнего выражения в сработавшей ветке.
3. Циклические конструкции
В Ruby существует несколько способов организации повторяющихся вычислений: традиционные управляющие конструкции (while, until, loop), а также итераторы — методы, принимающие блоки кода. Последние составляют основу идиоматического стиля Ruby и предпочтительны в большинстве случаев, поскольку обеспечивают лучшую читаемость, инкапсуляцию логики и устойчивость к ошибкам (например, забытому изменению счётчика).
3.1. while и until
Конструкции while и until синтаксически схожи и отличаются только условием продолжения.
Синтаксис while:
while условие
# тело цикла
end
Цикл выполняется, пока условие истинно (не false и не nil). Проверка условия происходит перед каждой итерацией. Если условие изначально ложно, тело цикла не выполнится ни разу.
Синтаксис until:
until условие
# тело цикла
end
Цикл выполняется, пока условие ложно. Эквивалентно while !(условие), но выразительнее в случаях вроде «пока пользователь не ввёл корректные данные».
Модификаторы while и until (постфиксная форма):
x = 0
x += 1 while x < 10
Обратите внимание: в постфиксной форме тело цикла выполняется хотя бы один раз, даже если условие изначально ложно, — но только если тело представляет собой одно выражение. В случае составного тела (через begin/end) поведение меняется.
Особый случай: begin/end while
begin
puts "Введите число:"
input = gets.chomp
end while input.empty?
Такая форма гарантирует, что тело выполнится минимум один раз — аналог do/while в C-подобных языках. Однако в Ruby 3.0+ такая конструкция считается устаревшей и вызывает предупреждение (warning: (...) while (...) without rescue is a syntax error в будущем), поэтому предпочтительно использовать loop + break.
3.2. loop
Бесконечный цикл, прерываемый явно через break, return или exit:
loop do
# тело
break if условие_выхода
end
Конструкция loop — единственная в Ruby, гарантированно реализованная как встроенная (не метод), что обеспечивает максимальную производительность. Она часто используется как основа для пользовательских циклов с нетривиальной логикой выхода.
Преимущества loop:
- Чётко выражает намерение: «повторять бесконечно, пока не будет явного выхода»;
- Позволяет использовать
breakс возвратом значения:result = loop do
input = gets.chomp
break input unless input.empty?
end
3.3. Итераторы — основа циклических операций в Ruby
В Ruby итерация реализована через методы-итераторы, которые принимают блоки (block) — анонимные функции, ограниченные по времени жизни вызовом итератора. Эта модель позволяет абстрагироваться от деталей управления счётчиком, граничными условиями и инкрементом.
3.3.1. Integer#times
Повторяет блок заданное количество раз. Передаёт в блок текущий индекс (от 0 до n-1):
5.times { |i| puts "Итерация #{i}" }
# → 0, 1, 2, 3, 4
Эквивалентно for i in 0...5, но без создания переменной цикла в окружающей области видимости.
3.3.2. Integer#downto и Integer#upto
Генерируют последовательность целых чисел в указанном диапазоне:
5.downto(1) { |i| puts i } # → 5, 4, 3, 2, 1
1.upto(5) { |i| puts i } # → 1, 2, 3, 4, 5
Работают и с нецелыми числами, но только если шаг равен 1 (иначе — ошибка). Для дробных шагов используются другие подходы.
3.3.3. Range#each
Перебирает все элементы диапазона. Реализован для числовых и символьных диапазонов:
('a'..'e').each { |c| print c } # → abcde
(1..3).each { |n| puts n } # → 1, 2, 3
Важно: для больших или бесконечных диапазонов (1..Float::INFINITY) вызов each приведёт к зависанию — в таких случаях используют lazy (ленивые вычисления).
3.3.4. Array#each, Hash#each и другие коллекции
Стандартные коллекции предоставляют метод each, который применяет блок к каждому элементу:
[1, 2, 3].each { |x| puts x * 2 } # → 2, 4, 6
{a: 1, b: 2}.each { |k, v| puts "#{k}=#{v}" } # → a=1, b=2
Поведение each определено протоколом Enumerable, который реализуется почти всеми стандартными коллекциями. При реализации пользовательских коллекций достаточно определить each, чтобы получить доступ к map, select, reduce и другим методам.
3.3.5. Контроль потока внутри блока: next, break, redo
Блоки поддерживают специальные ключевые слова для управления итерацией:
-
next— завершает текущую итерацию и переходит к следующей (аналогcontinueв C/Java). Может возвращать значение, передаваемое итератору:(1..5).map { |x| next 0 if x.even?; x } # → [1, 0, 3, 0, 5] -
break— прерывает итерацию и возвращает управление за пределы итератора. Может принимать значение, которое станет результатом всего выражения итератора:result = [1, 2, 3, 4, 5].each { |x| break x * 10 if x > 3 }
# result → 40 -
redo— повторяет текущую итерацию с самого начала, не изменяя состояние итератора (например, не увеличивая счётчик). Опасен — может привести к бесконечному циклу при неосторожном использовании:count = 0
3.times do |i|
count += 1
redo if count < 5
puts "i=#{i}, count=#{count}"
end
# Выведет три строки, но count будет ≥5 к моменту вывода
3.3.6. Ленивые итераторы (lazy)
Для работы с потенциально бесконечными или очень большими последовательностями Ruby предоставляет ленивые вычисления через Enumerator::Lazy:
result = (1..Float::INFINITY)
.lazy
.select(&:even?)
.map { |x| x ** 2 }
.first(5)
# → [4, 16, 36, 64, 100]
Здесь цепочка методов (select, map) не выполняется сразу, а откладывается до вызова first, который запрашивает ровно пять элементов. Это позволяет эффективно обрабатывать потоки данных без избыточного потребления памяти.
3.4. for — устаревшая конструкция
Синтаксис:
for переменная in коллекция
# тело
end
Семантически эквивалентен collection.each { |переменная| ... }, но с важным отличием: переменная переменная создаётся в текущей области видимости, а не в локальной области блока. Это может привести к неожиданным эффектам перезаписи переменных.
Пример:
x = "внешняя"
for x in [1, 2, 3]
# ...
end
puts x # → 3 (переменная x перезаписана)
В идиоматическом Ruby конструкция for почти не используется — предпочтение отдаётся each.
4. Идиомы и лучшие практики
4.1. Предпочтение итераторов перед while/for
Итераторы:
- исключают ошибки в управлении счётчиком («на единицу больше/меньше»);
- обеспечивают локальность переменных;
- позволяют легко комбинировать трансформации (
map,select,reduce); - естественно интегрируются с функциональным стилем.
Пример «неправильного» цикла:
i = 0
result = []
while i < array.length
result << array[i] * 2 if array[i].even?
i += 1
end
Идиоматическая замена:
result = array.select(&:even?).map { |x| x * 2 }
4.2. Использование case вместо цепочек if/elsif
Когда проверяется одно и то же значение по нескольким условиям, case читается яснее и быстрее работает (благодаря возможной оптимизации через хэши или таблицы переходов в некоторых реализациях).
4.3. Избегание побочных эффектов в условиях
Условия должны быть чистыми — без изменения состояния. Например, избегайте:
if (x = get_value) && x > 0 # присваивание внутри условия
Лучше:
x = get_value
if x && x > 0
Это повышает предсказуемость и облегчает отладку.
4.4. nil-безопасность через &. (safe navigation operator)
Начиная с Ruby 2.3, оператор &. позволяет безопасно вызывать методы у потенциально nil-объектов:
user&.profile&.name
# эквивалентно:
user && user.profile && user.profile.name
Уменьшает многословность защитных проверок.
5. Приоритеты операторов в Ruby
Понимание приоритета операторов критично для корректного чтения и написания выражений без избыточных скобок. В Ruby приоритеты строго фиксированы и не зависят от контекста.
Ниже приведён список операторов в порядке убывания приоритета (сверху — самый высокий):
| Приоритет | Операторы | Примечания |
|---|---|---|
| 1 | :: | Разрешение области видимости (например, Math::PI) |
| 2 | [], []= | Доступ и присваивание по индексу/ключу |
| 3 | ** | Возведение в степень (правоассоциативный: 2 ** 3 ** 2 = 2 ** 9) |
| 4 | унарные +, -, !, ~ | +5, -x, !condition, ~mask |
| 5 | *, /, % | Арифметические: умножение, деление, модуль |
| 6 | +, - | Сложение и вычитание |
| 7 | <<, >> | Побитовые сдвиги |
| 8 | & | Побитовое И |
| 9 | |, ^ | Побитовое ИЛИ, исключающее ИЛИ |
| 10 | <, <=, >, >=, <=>, ==, ===, !=, =~, !~ | Операторы сравнения и сопоставления |
| 11 | && | Логическое И (высокий приоритет) |
| 12 | || | Логическое ИЛИ (высокий приоритет) |
| 13 | .., ... | Диапазоны |
| 14 | ? : | Тернарный условный оператор |
| 15 | =, +=, -=, и др. составные присваивания | Все формы присваивания (низкий приоритет) |
| 16 | not | Логическое НЕ (низкоприоритетное) |
| 17 | and, or | Логические И/ИЛИ (низкоприоритетные) |
| 18 | defined? | Проверка определённости |
| 19 | if, unless, while, until (в постфиксной форме) | Модификаторы — самый низкий приоритет |
Важные следствия:
a = b && cинтерпретируется какa = (b && c), а не(a = b) && c.
Это означает: сначала вычисляетсяb && c, затем результат присваиваетсяa.a = b and c→(a = b) and c. Здесь присваивание происходит первым, затем результат присваивания (значениеb) участвует вand c.x = y ? a : b→x = (y ? a : b)— тернарный оператор выше присваивания.
Рекомендация: избегайте смешивания and/or с присваиванием без скобок. При сомнениях — используйте &&/|| или явные скобки.
6. Сравнительный анализ: Ruby vs Python vs JavaScript
| Аспект | Ruby | Python | JavaScript |
|---|---|---|---|
| Операторы как методы | Да: a + b → a.+(b) | Нет: + — встроенная операция (но __add__ для перегрузки) | Нет: + — встроенный, перегрузка невозможна |
| Логическое «и» | && (высокий приоритет), and (низкий) | and (единственный, приоритет ниже !=, выше not) | && (единственный) |
| Условное присваивание | ` | =(«nil-coalescing assignment»),&&=` | |
| Нулевое слияние | Нет оператора ??, но `x | yчасто используется (результат —y, если x` ложно) | |
| Циклы по коллекции | each, map, select (итераторы + блоки) | for item in list:, map(), filter() | for-of, forEach, map, filter |
Условие в while | Любое значение: только false/nil — ложные | Любое значение: 0, "", [], None — ложные | Любое значение: 0, "", null, undefined, NaN, false — falsy |
| Оператор «космический корабль» | <=> (встроен, возвращает -1, 0, 1, nil) | Нет (но cmp() в Python 2, или (a > b) - (a < b)) | Нет (но можно реализовать) |
| Диапазоны | 1..5, 1...5 — объекты Range | range(1, 6) — генератор/последовательность | Нет (только через массивы или for (let i = 1; i <= 5; i++)) |
| Безопасная навигация | &. (начиная с 2.3) | Нет (но getattr(obj, 'attr', default)) | ?. (ES2020) |
Ключевое отличие Ruby: единообразие через объектность. Поскольку всё — объект, поведение операторов можно расширять. Например, можно определить Vector#* для скалярного умножения, и v * 2 будет работать интуитивно. В Python перегрузка тоже возможна, но через специальные методы (__mul__); в JavaScript — невозможно без изменения прототипов (что небезопасно).
7. Типичные ошибки и диагностика
7.1. Перепутывание and/or с &&/||
Ошибка:
user = find_user && user.active? # OK: сначала find_user, затем active?
user = find_user and user.active? # ОПАСНО: эквивалентно (user = find_user) and user.active?
Если find_user возвращает nil, вторая часть не выполнится — но если возвращает объект, user.active? вызовется и проигнорируется (результат не присваивается никуда).
Диагностика: RuboCop выдаёт предупреждение Style/AndOr при использовании and/or вне управляющих конструкций.
7.2. Изменение коллекции внутри each
Ошибка:
arr = [1, 2, 3, 4]
arr.each { |x| arr.delete(x) if x.even? }
# Результат: [1, 3] — но итерация пропустит элементы!
При удалении элементов индексы смещаются, и each «перепрыгивает» через следующий элемент.
Решение: использовать delete_if, reject!, select — методы, предназначенные для фильтрации:
arr.delete_if(&:even?)
7.3. Использование for и утечка переменной
Ошибка:
name = "глобальное"
for name in ["Алиса", "Боб"]
# ...
end
puts name # → "Боб"
Переменная name перезаписана.
Решение: заменить на each:
["Алиса", "Боб"].each { |name| ... } # name локальна в блоке
7.4. x ||= y и ложные, но значимые значения
Ошибка:
count ||= 10
Если count == 0, выражение присвоит 10, хотя 0 — валидное значение.
Решение: проверять явно на nil:
count = 10 if count.nil?
# или
count = defined?(count) && count.nil? ? 10 : count # избыточно
# лучший вариант — инициализировать при объявлении
7.5. Сравнение == и equal?
Ошибка:
a = "hello"
b = "hello"
a == b # → true
a.equal?(b) # → false (разные объекты)
equal? проверяет тождественность объектов (один и тот же object_id), а не равенство содержимого.
Когда использовать:
==— для семантического равенства,equal?— для проверки, ссылается ли переменная на тот же самый объект (редко, в основном в метапрограммировании или тестах).
8. Практикум (необязательное приложение)
Для закрепления материала рекомендуются следующие упражнения. Каждое можно реализовать в виде отдельной задачи в обучающем курсе.
Упражнение 1. Реализация fizz_buzz тремя способами
- С
while - С
loop+break - С
(1..n).each
Сравните читаемость, длину кода, риск ошибок.
Упражнение 2. Безопасное вычисление цепочки методов
Дан объект:
user = { profile: { settings: { theme: "dark" } } }
Напишите выражение, возвращающее theme или "default", если любой уровень nil.
Решения:
- С вложенными
&& - С
&.(safe navigation) - С
dig(встроенный методHash#dig)
Упражнение 3. Перегрузка оператора
Создайте класс Vector2D с координатами x, y. Реализуйте:
+— покомпонентное сложение,*— умножение на скаляр (если аргумент — число) и скалярное произведение (если —Vector2D),<=>для сортировки по длине вектора.
Упражнение 4. Ленивая фильтрация бесконечной последовательности
Сгенерируйте первые 10 простых чисел, начиная с 2, с использованием lazy.